﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Timers;
using Thor.Common;
using Thor.Domain.Configuration;

namespace Thor.Domain
{
    public delegate void CalendarChangedEventHandler(object sender, CalendarChangedEventArgs e);

    /// <summary>
    /// Calendar monitoring class.
    /// </summary>
    public class CalendarMonitor
    {
        private CalendarMonitorParameters _calendarMonitorParameters = null;
        private Calendar _calendar = null;
        private bool _isRunning = false;
        private bool _isPaused = false;
        private Timer _timer = null;

        /// <summary>
        /// Calendar changed event.
        /// </summary>
        public event CalendarChangedEventHandler CalendarChanged;

        /// <summary>
        /// Starting event.
        /// </summary>
        public event EventHandler Starting;

        /// <summary>
        /// Started event.
        /// </summary>
        public event EventHandler Started;

        /// <summary>
        /// Stopping event.
        /// </summary>
        public event EventHandler Stopping;

        /// <summary>
        /// Stopped event.
        /// </summary>
        public event EventHandler Stopped;

        /// <summary>
        /// Pausing event.
        /// </summary>
        public event EventHandler Pausing;

        /// <summary>
        /// Paused event.
        /// </summary>
        public event EventHandler Paused;

        /// <summary>
        /// Resuming event.
        /// </summary>
        public event EventHandler Resuming;

        /// <summary>
        /// Resumed event.
        /// </summary>
        public event EventHandler Resumed;

        public CalendarMonitor(CalendarMonitorParameters calendarMonitorParameters)
        {
            if (calendarMonitorParameters == null)
                throw new Exception("Parameters cannot be null.");

            calendarMonitorParameters.Validate();

            _calendarMonitorParameters = calendarMonitorParameters;
        }

        /// <summary>
        /// Indicates whether or not the monitor is running.
        /// </summary>
        public bool IsRunning
        {
            get { return _isRunning; }
            private set { _isRunning = value; }
        }

        /// <summary>
        /// Starts monitoring the calendar.
        /// </summary>
        public void Start()
        {
            if (!_isRunning && !_isPaused)
            {
                OnStarting();

                double secondsInterval = _calendarMonitorParameters.Interval * 60000;

                if (_calendarMonitorParameters.LoadOnStart)
                    GetCalendar();

                _timer = new Timer(secondsInterval);
                _timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);
                _timer.Start();

                _isRunning = true;

                OnStarted();
            }
        }

        /// <summary>
        /// Stops monitoring the calendar.
        /// </summary>
        public void Stop()
        {
            if (_isRunning || _isPaused)
            {
                OnStopping();

                _timer.Stop();
                _timer.Dispose();
                _timer = null;

                _calendar = null;

                _isRunning = false;

                OnStopped();
            }
        }

        /// <summary>
        /// Pauses calendar monitoring.
        /// </summary>
        public void Pause()
        {
            if (_isRunning && !_isPaused)
            {
                OnPausing();

                _timer.Stop();
                _isRunning = false;
                _isPaused = true;

                OnPaused();
            }
        }

        /// <summary>
        /// Resumes calendar monitoring.
        /// </summary>
        public void Resume()
        {
            if (!_isRunning && _isPaused)
            {
                OnResuming();

                _timer.Start();
                _isRunning = true;
                _isPaused = false;

                OnResumed();
            }
        }

        private void GetCalendar()
        {
            DateTime dateTimeUtc = DateTime.UtcNow;

            TimeZoneManager timeZoneManager = new TimeZoneManager();
            TimeZoneInfo timeZoneInfo = timeZoneManager.GetTimeZoneInfoById(_calendarMonitorParameters.Calendar.TimeZone);

            DateTime dateTimeLocal = timeZoneManager.ConvertTimeFromUtc(dateTimeUtc, _calendarMonitorParameters.Calendar.TimeZone);
            
            DateTime startDateTime;
            DateTime endDateTime;

            bool datesSpecified = (_calendarMonitorParameters.Request.StartDate.HasValue && _calendarMonitorParameters.Request.EndDate.HasValue);
            bool resetDates = !datesSpecified;

            if (!_calendarMonitorParameters.Request.StartDate.HasValue)
                startDateTime = new DateTime(dateTimeLocal.Year, dateTimeLocal.Month, dateTimeLocal.Day);
            else
                startDateTime = _calendarMonitorParameters.Request.StartDate.Value;

            if (!_calendarMonitorParameters.Request.EndDate.HasValue)
                endDateTime = new DateTime(dateTimeLocal.Year, dateTimeLocal.Month, dateTimeLocal.Day).AddHours(23).AddMinutes(59).AddSeconds(59);
            else
                endDateTime = _calendarMonitorParameters.Request.EndDate.Value;

            DateTime localStartDateTime = DateTime.SpecifyKind(startDateTime, DateTimeKind.Unspecified);
            DateTime localEndDateTime = DateTime.SpecifyKind(endDateTime, DateTimeKind.Unspecified);

            _calendarMonitorParameters.Request.StartDate = TimeZoneInfo.ConvertTimeToUtc(localStartDateTime, timeZoneInfo);
            _calendarMonitorParameters.Request.EndDate = TimeZoneInfo.ConvertTimeToUtc(localEndDateTime, timeZoneInfo);

            StringBuilder traceMessage = new StringBuilder();
            traceMessage.AppendLine("Thor.Domain.CalendarMonitor.GetCalendar");
            traceMessage.AppendLine(string.Format("=> UtcTime: {0}", dateTimeUtc.ToString()));
            traceMessage.AppendLine(string.Format("=> LocalTime: {0}", dateTimeLocal.ToString()));
            traceMessage.AppendLine(string.Format("=> Calendar: {0}", _calendarMonitorParameters.Calendar.CalendarEmail));
            traceMessage.AppendLine(string.Format("=> TimeZone: {0}", _calendarMonitorParameters.Calendar.TimeZone));
            traceMessage.AppendLine(string.Format("=> StartDateTime: {0}", startDateTime.ToString()));
            traceMessage.AppendLine(string.Format("=> EndDateTime: {0}", endDateTime.ToString()));
            Trace.TraceInformation(traceMessage.ToString());

            if (datesSpecified)
            {
                if (dateTimeUtc > _calendarMonitorParameters.Request.EndDate.Value)
                {
                    Stop();
                    OnCalendarChanged(new CalendarChangedEventArgs(_calendar));
                    return;
                }
            }

            var updatedCalendar = _calendarMonitorParameters.Provider.GetCalendar(_calendarMonitorParameters.Request);

            updatedCalendar.Id = _calendarMonitorParameters.Calendar.Id;
            updatedCalendar.TimeZone = _calendarMonitorParameters.Calendar.TimeZone;
            updatedCalendar.PublicCanBook = _calendarMonitorParameters.Calendar.PublicCanBook;
            updatedCalendar.PublicCanView = _calendarMonitorParameters.Calendar.PublicCanView;
            updatedCalendar.DisplaySubject = _calendarMonitorParameters.Calendar.DisplaySubject;
            updatedCalendar.UseBookingAgent = _calendarMonitorParameters.Calendar.UseBookingAgent;
            updatedCalendar.UseTimeframe = _calendarMonitorParameters.Calendar.UseTimeframe;
            updatedCalendar.StartTime = _calendarMonitorParameters.Calendar.StartTime;
            updatedCalendar.EndTime = _calendarMonitorParameters.Calendar.EndTime;

            updatedCalendar.ConvertFromUtc();

            bool hasChanges = false;

            if (_calendar == null && updatedCalendar != null)
            {
                hasChanges = true;
                _calendar = updatedCalendar;

            }
            else if (_calendar != null && updatedCalendar == null)
            {
                hasChanges = true;
                _calendar = updatedCalendar;
            }
            else
            {
                if (!_calendar.Equals(updatedCalendar))
                {
                    hasChanges = true;
                    _calendar = updatedCalendar;
                }
            }

            if (hasChanges)
                OnCalendarChanged(new CalendarChangedEventArgs(_calendar));

            if (resetDates)
            {
                _calendarMonitorParameters.Request.StartDate = null;
                _calendarMonitorParameters.Request.EndDate = null;
            }
        }

        /// <summary>
        /// Updates the calendar.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            _timer.Stop();

            GetCalendar();

            _timer.Start();
        }

        /// <summary>
        /// Invokes the CalendarChanged event. Called whenever the calendar changes.
        /// </summary>
        /// <param name="e"></param>
        private void OnCalendarChanged(CalendarChangedEventArgs e)
        {
            if (CalendarChanged != null)
                CalendarChanged(this, e);
        }

        /// <summary>
        /// Invokes the starting event.
        /// </summary>
        private void OnStarting()
        {
            if (Starting != null)
                Starting(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the started event.
        /// </summary>
        private void OnStarted()
        {
            if (Started != null)
                Started(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the stopping event.
        /// </summary>
        private void OnStopping()
        {
            if(Stopping != null)
                Stopping(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the stopped event.
        /// </summary>
        private void OnStopped()
        {
            if(Stopped != null)
                Stopped(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the pausing event.
        /// </summary>
        private void OnPausing()
        {
            if(Pausing != null)
                Pausing(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the paused event.
        /// </summary>
        private void OnPaused()
        {
            if(Paused != null)
                Paused(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the resuming event.
        /// </summary>
        private void OnResuming()
        {
            if(Resuming!= null)
                Resuming(this, new EventArgs());
        }

        /// <summary>
        /// Invokes the resumed event.
        /// </summary>
        private void OnResumed()
        {
            if(Resumed != null)
                Resumed(this, new EventArgs());
        }
    }

    public enum CalendarMonitorStatus
    {
        Running,
        Stopped,
        Paused
    }

    public class CalendarMonitorParameters
    {
        public CalendarProviderBase Provider { get; set; }
        public Calendar Calendar { get; set; }
        public CalendarRequest Request { get; set; }
        public int Interval { get; set; }
        public bool LoadOnStart { get; set; }

        public void Validate()
        {
            if (Provider == null)
                throw new Exception("CalendarMonitorParameters.Provider cannot be null.");

            if (Calendar == null)
                throw new Exception("CalendarMonitorParameters.Calendar cannot be null.");

            if (Request == null)
                throw new Exception("CalendarMonitorParameters.Request cannot be null.");

            if (Interval < 1 || Interval > 1440)
                throw new Exception("CalendarMonitorParameters.Interval must be greater than 1 and less than 1440.");
        }
    }
}
